﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Dtd.Schema;
using System.Diagnostics;
using Engine.Sgml.Merging;
using Engine.Sgml.Matching;

namespace Engine.Sgml.Assembling {
    internal sealed class ElementAssembler {
        public static readonly string DefaultElementValue = "Undefined";

        public bool CanBuild(IModelGroupsNode groupItem, DElementDeclaration ancestorDeclaration) {
            Guard.ArgumentNotNull(groupItem, "groupItem");
            Guard.ArgumentNotNull(ancestorDeclaration, "ancestorDeclaration");
            // Если элемент группы имеет туже декларацию что и узел предок
            // Создание узла вызовет образование безконечного цикла
            if (groupItem.HasDeclaration())
                if (object.ReferenceEquals(ancestorDeclaration, groupItem.Declaration))
                    // Если элемент группы невозможно пропустить
                    // разрешить создание узла
                    return !groupItem.CanSkipped();
                else
                    return true;
            else if (groupItem.Name == DName.PCData || groupItem.Name == DName.CData)
                return true;
            else
                return false;
            }

        public bool BuildRequired(SElement element) {
            // Builds a conent for the specified element accoring with its declaration.
            if (element.Declaration == null ||
                element.ContentModelType() == DContentModelType.Empty)
                return false;
            // Предотвратить образование безконечного цикла
            if (object.ReferenceEquals(element.Declaration, element.Parent.Declaration))
                return false;
            DModelGroups modelGroups = element.Declaration.ContentModel.Groups;
            // Если группа модели должна пристутствовать хотя бы один раз
            return modelGroups.MustPresent();
            }

        public SElement Build(IModelGroupsNode model, SElement ancestor, int position) {
            Debug.Assert(model.Name != null);
            ancestor.IsEmpty = false;
            SElement element = ancestor.Nodes.Push();
            element.Clear();
            element.Parent = ancestor;
            element.Name = model.Name.ToString();
            element.DName = model.Name;
            element.Declaration = model.Declaration;
            // Set the line number and line position
            SetLineInfo(element, GetPreviousElement(ancestor, element));
            // Trace construction
            // TraceHelper.TraceConstructedElement(element, ancestor, position);
            // Set the type of node and its value
            if (model.Name == DName.PCData) {
                element.NodeType = SgmlNodeType.Text;
                element.Value = DefaultElementValue;
                }
            else if (model.Name == DName.CData) {
                element.NodeType = SgmlNodeType.CData;
                element.Value = DefaultElementValue;
                }
            else
                element.NodeType = SgmlNodeType.Element;
            // Move to position
            ancestor.Nodes.Move(element.OrderIndex, position);
            return element;
            }

        public void Build(DModelGroups model, SElement ancestor) {
            switch (model.OrderType) {
                case DGroupOrderType.OnlyOne:
                    BuildOnlyOneModelContent(ancestor, model);
                    break;
                case DGroupOrderType.Sequence:
                    BuildSequenceModelContent(ancestor, model);
                    break;
                case DGroupOrderType.AnyOrder:
                    BuildAnyOrderModelContent(ancestor, model);
                    break;
                default:
                    throw new NotSupportedException("Unknown the order type of a model groups.");
                }
            }

        private void BuildOnlyOneModelContent(SElement ancestor, DModelGroups model) {
            Debug.Assert(model.OrderType == DGroupOrderType.OnlyOne);
            for (int i = 0; i < model.Count; i++) {
                IModelGroupsNode groupItem = model[i];
                if (groupItem.MustPresent()) {
                    if (groupItem.IsModelGroups()) {
                        Build(groupItem.FirstItem.Parent, ancestor);
                        return;
                        }
                    else if (CanBuild(groupItem, ancestor.Declaration)) {
                        BuildRecursive(groupItem, ancestor);
                        return;
                        }
                    }
                }
            for (int i = 0; i < model.Count; i++) {
                IModelGroupsNode groupItem = model[i];
                if (groupItem.IsModelGroups()) {
                    Build(groupItem.FirstItem.Parent, ancestor);
                    break;
                    }
                else if (CanBuild(groupItem, ancestor.Declaration)) {
                    BuildRecursive(groupItem, ancestor);
                    break;
                    }
                }
            Debug.Assert(ancestor.Nodes.Count > 0);
            }

        private void BuildAnyOrderModelContent(SElement ancestor, DModelGroups model) {
            Debug.Assert(model.OrderType == DGroupOrderType.AnyOrder);
            for (int i = 0; i < model.Count; i++) {
                IModelGroupsNode groupItem = model[i];
                if (groupItem.MustPresent()) {
                    if (groupItem.IsModelGroups())
                        Build(groupItem.FirstItem.Parent, ancestor);
                    else if (CanBuild(groupItem, ancestor.Declaration)) {
                        BuildRecursive(groupItem, ancestor);
                        }
                    }
                }
            Debug.Assert(ancestor.Nodes.Count > 0);
            }

        private void BuildSequenceModelContent(SElement ancestor, DModelGroups model) {
            Debug.Assert(model.OrderType == DGroupOrderType.Sequence);
            for (int i = 0; i < model.Count; i++) {
                IModelGroupsNode groupItem = model[i];
                if (groupItem.MustPresent()) {
                    if (groupItem.IsModelGroups())
                        Build(groupItem.FirstItem.Parent, ancestor);
                    else if (CanBuild(groupItem, ancestor.Declaration))
                        BuildRecursive(groupItem, ancestor);
                    }
                }
            Debug.Assert(ancestor.Nodes.Count > 0);
            }

        private void BuildRecursive(IModelGroupsNode model, SElement ancestor) {
            SElement element = Build(model, ancestor, ancestor.Nodes.Count);
            if (BuildRequired(element))
                Build(element.Declaration.ContentModel.Groups, element);
            }

        private static SElement GetPreviousElement(SElement ancestor, SElement element) {
            SElement previous = element.PreviousNode;
            if (previous == null)
                previous = ancestor;
            return previous;
            }

        private static void SetLineInfo(SElement element, SElement previous) {
            element.LineNumber = previous.LineNumber + 1;
            element.LinePosition = previous.LinePosition;
            }
        }
    }
